/*
v0.0.1
*/


(function(){
	"use strict";
	
	//factor of converting 1 degree to radians. 
	Math.DEG2RAD = Math.PI / 180;
	
	//hack setTimeout function
	var _setTimeout = window.setTimeout;
	window.setTimeout = function(callback, timeout, param){
    	var args = Array.prototype.slice.call(arguments, 2);
        _setTimeout(function(){
        	callback.apply(null, args);
    	}, timeout);
	};
	
	var LibreGE = function(canvasElement, contextType){
		//cache this
		var self = this;
		
		/*
		 * initialize render context
		 */
		var renderContext;
		if(contextType === '2d'){
			renderContext = canvasElement.getContext("2d");
		}else{
			renderContext = canvasElement.getContext("experimental-webgl");
			renderContext.viewport(0, 0, canvasElement.width, canvasElement.height);
		}
		this.getRenderContext = function(){
			return renderContext;
		};
		

		/*
		 * register system event
		 */
		var typeList = ['contextmenu', 'mousedown', 'mousemove', 'mouseup', 'mousewheel', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'gesturestart', 'gestureend', 'gesturechange'];
		var listener = function(e){
			e.preventDefault();
			var msg = {
				timeStamp : e.timeStamp
			};
			switch(e.type){
				case 'mousedown' : {
					msg.type = LibreGE.msg.POINTER_DOWN;
					msg.x = e.layerX;
					msg.y = e.layerY;
					self.sendMessage(msg);
					break;
				}
				case 'mousemove' : {
					msg.type = LibreGE.msg.POINTER_MOVE;
					msg.x = e.layerX;
					msg.y = e.layerY;
					self.sendMessage(msg);
					break;
				}
				case 'mouseup' : {
					msg.type = LibreGE.msg.POINTER_UP;
					msg.x = e.layerX;
					msg.y = e.layerY;
					self.sendMessage(msg);
					break;
				}
				case 'mousewheel' : {
					msg.type = LibreGE.msg.MOUSE_WHEEL;
					self.sendMessage(msg);
					break;
				}
				default :{
					self.sendMessage(e);
				} 
			}			
			
			return false;
		};
		for(var property in typeList){
			canvasElement.addEventListener(typeList[property], listener, false);				
		}


		/*
		 * message management
		 */
		var msgQueue = [];
		var msgListenerList = [];

		var processMessageQueue = function(){
			var queue = msgQueue;
			msgQueue = [];
			for(var i in queue){
				self.sendMessage(queue[i]);
			}
		};
		
		this.registerMessageListener = function(listener){
			var found = false;
			for(var i in msgListenerList){
				if(msgListenerList[i] == listener){
					found = true;
					break;
				}
			}
			if(!found){
				msgListenerList.push(listener);
			}
			return this;
		};

		this.removeMessageListener = function(listener){
			var list = [];
			for(var i in msgListenerList){
				if(msgListenerList[i] != listener){
					list.push(msgListenerList[i]);
				}
			}
			msgListenerList = list;
			return this;
		};

		this.sendMessage = function(msg){
			var listenerList = msgListenerList.slice(0);
			for(var i in listenerList){
				if(msgListenerList[i].onMessage(msg)){
					break;
				}
			}
			return this;
		};
		
		this.postMessage = function(msg){
			msgQueue.push(msg);
			return this;
		};


		/*
		 * scene management
		 */
		var currentScene = null;
		this.getCurrentScene = function(){
			return currentScene;
		};
		this.setCurrentScene = function(scene){
			self.registerMessageListener(scene);
			
			self.sendMessage({
				'type' : LibreGE.msg.SCENE_BEFORE_EXIT,
				'exit' : currentScene,
				'enter' : scene
			});
			
			var lastScene = currentScene;
			currentScene = scene;
			
			self.sendMessage({
				'type' : LibreGE.msg.SCENE_AFTER_ENTER,
				'exit' : lastScene,
				'enter' : currentScene
			});
			
			self.removeMessageListener(lastScene);
			return this;
		};


		/*
		 * render management
		 */
		var running = false;
		var doUpdate = function(elapsed){
			this.update(elapsed, self);
			
			if(this.sortfunc){
				this.children.sort(this.sortfunc);
			}
			
			for (var i in this.children){
				doUpdate.call(this.children[i], elapsed, self);
			}
		};
		var doDraw = function(context){
			if(this.visible){
				this.draw(context, self);
				
				for (var i in this.children){
					doDraw.call(this.children[i], context, self);
				}					
			}
		};
		var onFrame = function(context, elapsed){
			//frame loop
			if(currentScene){
				doUpdate.call(currentScene, elapsed);
				doDraw.call(currentScene, context);
			}
		};


		//render next frame when browser calls back or in 1ms.
		var requestAnimationFrame = 
			window.requestAnimationFrame ||
			window.webkitRequestAnimationFrame ||
			window.mozRequestAnimationFrame ||
			window.msRequestAnimationFrame ||
			window.oRequestAnimationFrame ||
			function(callback) {
				setTimeout(callback, 1);
			};
		
		
		this.fps = 0;
		var elapsedSum = 0;
		var lastFrameTime = null;
		var frameCount = 0;
		function render(time){
			if(running){
				var now = time || (new Date()).getTime();
				var timeElapsed = now - lastFrameTime;
				lastFrameTime = now;
				
				processMessageQueue();
				
				onFrame(renderContext, timeElapsed);
				
				elapsedSum += timeElapsed;
				frameCount ++;
				if(elapsedSum >= 1000){
					self.fps = frameCount;
					elapsedSum -= 1000;
					frameCount = 0;
				}
				
				requestAnimationFrame(render, canvasElement);
			}
		};

		//start frame loop
		this.start = function(){
			if(!running){
				running = true;
				lastFrameTime = (new Date()).getTime();
				requestAnimationFrame(render, canvasElement);
			}
			return this;
		};
		
		//stop render
		this.stop = function(){
			running = false;
			return this;
		};
		
		//update one frame
		this.update = function(){
			lastFrameTime = (new Date()).getTime();
			requestAnimationFrame(render, canvasElement);
			return this;
		};
		
		//start immediately by default
		this.start();
	};
	
	
	/*
	 * system message define
	 */
	LibreGE.msg = {
		//scene event
		SCENE_BEFORE_EXIT : 'SCENE_BEFORE_EXIT',
		SCENE_AFTER_ENTER : 'SCENE_AFTER_ENTER',
		//virtual hardware event
		COMMAND_MENU : 'COMMAND_MENU',
		COMMAND_BACK : 'COMMAND_BACK',
		POINTER_DOWN : 'POINTER_DOWN',
		POINTER_MOVE : 'POINTER_MOVE',
		POINTER_UP : 'POINTER_UP',
		POINTER_CANCEL : 'POINTER_CANCEL',
		POINTER_HOLD : 'POINTER_HOLD',
		GESTURE_START : 'GESTURE_START',
		GESTURE_END : 'GESTURE_END',
		GESTURE_CHANGE : 'GESTURE_CHANGE',
		MOUSE_WHEEL : 'MOUSE_WHEEL',
		KEY_DOWN : 'KEY_DOWN',
		KEY_UP : 'KEY_UP',
		KEY_PRESS : 'KEY_PRESS'
	};
	
	
	/*
	 * utilities
	 */
	LibreGE.utils = {};

	//works, but need time to load url.
	LibreGE.utils.include = function(url){
		var element;
		switch(url.split('.').pop()){
			case 'css' : {
				element = document.createElement('link');
				element.setAttribute('rel','stylesheet');
				element.setAttribute('type','text/css');
				element.setAttribute('href',url);
				break;
		    }
		    case 'js' : {
		    	element=document.createElement('script');
		    	element.setAttribute('language','javascript');
				element.setAttribute('type','text/javascript');
		    	element.setAttribute('src',url);
		    	break;
		    }
		    default :{
		    	window.console && window.console.error('could not identify', url, 'skip include');
		    	return;		    	
		    } 
		}
		var head = document.querySelector('head');
		if(head.innerHTML.indexOf(element.outerHTML) != -1){
			window.console && window.console.warn('Duplicate include, skipping:', url);
		}else{
		    head.appendChild(element);
		}
	};
	
	/*
	 * B(t) = (1-t) * (1-t) * (1-t) * P0 + 3 * (1-t) * (1-t) * t * P1 + 3 * (1-t) * t * t * P2 + t * t * t * P3
	 * 
	 * in Cubic Bezier Progression: P0 = (0) , P3 = (1)
	 * 
	 * B(t) = 3 * (1-t) * (1-t) * t * P1 + 3 * (1-t) * t * t * P2 + t * t * t
	 * 
	 */
	LibreGE.utils.cubicBezier = function(P0, P1, P2, P3, t){
		if(t == 0){
			return P0;
		}else if(t == 1){
			return P3;
		}

		var a = t * t;
		var b = (1 - t) * (1 - t);
		
		return b * (1-t) * P0 + 3 * b * t * P1 + 3 * (1-t) * a * P2 + a * t * P3;
	};
	LibreGE.utils.getCubicBezierProgression = function(cp1x, cp1y, cp2x, cp2y, t){
		
		if(t == 0){
			return 0;
		}else if(t == 1){
			return 1;
		}
		
		return LibreGE.utils.cubicBezier(0, cp1y, cp2y, 1, t);
	};
	LibreGE.utils.setProperties = function(target, obj){
		for(var p in obj){
			if(obj.hasOwnProperty(p)){
				target[p] = obj[p];				
			}
		}
	};
	LibreGE.utils.apply2DTransforms = function(context, transforms){
		if(typeof transforms === 'object' && transforms.constructor == Array){
			for(var index in transforms){
				var t = transforms[index];
				if(typeof t.translate === 'object'){
					context.translate(t.translate.x, t.translate.y);
				}else if(typeof t.rotate === 'number'){
					context.rotate(Math.DEG2RAD * t.rotate);				
				}else if(typeof t.scale === 'object'){
					context.scale(t.scale.x, t.scale.y);				
				}else if(typeof t.transform === 'object' && t.transform.constructor == Array){
					context.setTransform(t.transform[0], t.transform[1], t.transform[2], t.transform[3], t.transform[4], t.transform[5]);
				}
			}
		}
	};
	LibreGE.utils.apply2DShadow = function(context, shadow){
		if(typeof shadow === 'object'){
			context.shadowOffsetX = shadow.offsetX;
			context.shadowOffsetY = shadow.offsetY;
			context.shadowBlur = shadow.blur;
			context.shadowColor = shadow.color;
		}
	};
	LibreGE.utils.create2DShape = function(context, x, y, path){
		context.beginPath();
		context.moveTo(x, y);
		for(var i in path){
			var p = path[i];
			if(p[0] === 'moveTo'){
				context.moveTo(p[1], p[2]);				
			}else if(p[0] === 'lineTo'){
				context.lineTo(p[1], p[2]);
			}else if(p[0] === 'quadraticCurveTo'){
				context.quadraticCurveTo(p[1], p[2], p[3], p[4]);
			}else if(p[0] === 'bezierCurveTo'){
				context.bezierCurveTo(p[1], p[2], p[3], p[4], p[5], p[6]);
			}else if(p[0] === 'arcTo'){
				context.arcTo(p[1], p[2], p[3], p[4], p[5]);
			}else if(p[0] === 'rect'){
				context.rect(p[1], p[2], p[3], p[4]);
			}else if(p[0] === 'arc'){
				context.arc(p[1], p[2], p[3], p[4] * Math.DEG2RAD, p[5] * Math.DEG2RAD, p[6]);
			}else if(p[0] === 'close'){
				context.closePath();				
			}
		}
	};
	
	LibreGE.algorithms = {};
	
	LibreGE.algorithms.AStarPathFinding = function(){
		//TODO
	};
	
	/*
	 * Shader Program
	 */
	var getShader = function(gl, source, type){
        var shader = null;
        if (type == 'vertex-shader') {
            shader = gl.createShader(gl.VERTEX_SHADER);
        } else if (type == 'fragment-shader') {
            shader = gl.createShader(gl.FRAGMENT_SHADER);
        } else {
        	alert('unknown shader type!');
        }

        gl.shaderSource(shader, source);
        gl.compileShader(shader);

        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            alert(gl.getShaderInfoLog(shader));
            return null;
        }
        return shader;
	};

	var getNamesFromShaderSource = function(source, type){
		var reg = null;
		if(type === 'uniform'){
			reg = new RegExp("uniform[\t ]+((bool|int|float|[bi]?vec[234]|mat[234]?|sampler2D|samplerCube)[\t ]+([A-Za-z0-9]*))[\t ]*;","gi");
		}else if(type === 'attribute'){
			reg = new RegExp("attribute[\t ]+((bool|int|float|[bi]?vec[234]|mat[234])[\t ]+([A-Za-z0-9]*))[\t ]*;","gi");			
		}
		var result = null;
		var variables = [];
		while (result = reg.exec(source)){
			variables.push(result[3]);
		};
		return variables;
	};
	
	LibreGE.Program = function(vertexShaderSrc, fragmentShaderSrc){
		this.vertexShaderSrc = vertexShaderSrc;
		this.fragmentShaderSrc = fragmentShaderSrc;
		this.attribLocation = {};
		this.uniformLocation = {};
	};
	
	LibreGE.Program.prototype.initialize = function(gl){
		this.vertexShader = getShader(gl, this.vertexShaderSrc, 'vertex-shader');
		this.fragmentShader = getShader(gl, this.fragmentShaderSrc, 'fragment-shader');
		
        this.program = gl.createProgram();
        gl.attachShader(this.program, this.vertexShader);
        gl.attachShader(this.program, this.fragmentShader);
        gl.linkProgram(this.program);

        var src = this.vertexShaderSrc + this.fragmentShaderSrc;
        var attributeNames = getNamesFromShaderSource(src, 'attribute');
        var uniformNames = getNamesFromShaderSource(src, 'uniform');

        for(var p in attributeNames){
        	this.attribLocation[attributeNames[p]] = gl.getAttribLocation(this.program, attributeNames[p]);
   	        gl.enableVertexAttribArray(this.attribLocation[attributeNames[p]]);
        }
        
        
        for(var p in uniformNames){
       		this.uniformLocation[uniformNames[p]] = gl.getUniformLocation(this.program, uniformNames[p]);
        }
        
        if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
            alert('Could not initialise shader program.');
        }		
	};

	LibreGE.Program.prototype.use = function(gl){
		gl.useProgram(this.program);
	};

	LibreGE.Program.prototype.clearBuffer = function(gl){};
	
	LibreGE.Program.prototype.finalize = function(gl){
		this.clearBuffer(gl);
		gl.deleteShader(this.vertexShader);
		gl.deleteShader(this.fragmentShader);
		gl.deleteProgram(this.program);
	};
	
	LibreGE.Program.getTexture = function(gl, img){
		var texture = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, texture);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);  
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);  
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);  
		gl.generateMipmap(gl.TEXTURE_2D);
		return texture; 
	};
	
	LibreGE.program = {};
	
	LibreGE.program.simpleTexture = function(){
		var vertexShaderSrc = '\
		    uniform mat4 uPMatrix;\
		    uniform mat4 uMVMatrix;\
			\
			attribute vec3 aVertexPosition;\
			attribute vec2 aTextureCoord;\
			\
			varying highp vec2 vTextureCoord;\
			\
		    void main(void) {\
		        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\
				vTextureCoord = aTextureCoord;\
		    }';
		var fragmentShaderSrc = '\
			#ifdef GL_ES\n\
			precision highp float;\n\
			#endif\n\
			uniform float uAlpha;\
			uniform sampler2D uSampler;\
			\
			varying highp vec2 vTextureCoord;\
			\
		    void main(void) {\
				gl_FragColor = texture2D(uSampler, vTextureCoord);\
				gl_FragColor.r *= uAlpha;\
				gl_FragColor.g *= uAlpha;\
				gl_FragColor.b *= uAlpha;\
				gl_FragColor.a *= uAlpha;\
		    }';
		
		var p = new LibreGE.Program(vertexShaderSrc, fragmentShaderSrc);
		p.bindData = LibreGE.program.simpleTexture.prototype.bindData;
		p.clearBuffer = LibreGE.program.simpleTexture.prototype.clearBuffer;
		
		return p;
	};
	
	LibreGE.program.simpleTexture.prototype.bindData = function(gl, pMatrix, mvMatrix, vertexPosition, textureCoord, alpha, texture){
        gl.uniformMatrix4fv(this.uniformLocation['uPMatrix'], false, pMatrix);
        gl.uniformMatrix4fv(this.uniformLocation['uMVMatrix'], false, mvMatrix);
        
		this.vertexPositionBuffer = this.vertexPositionBuffer || gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexPositionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertexPosition, gl.STATIC_DRAW);
        gl.vertexAttribPointer(this.attribLocation['aVertexPosition'], 3, gl.FLOAT, false, 0, 0);
        
		this.textureCoordBuffer = this.textureCoordBuffer || gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.textureCoordBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, textureCoord, gl.STATIC_DRAW);
        gl.vertexAttribPointer(this.attribLocation['aTextureCoord'], 2, gl.FLOAT, false, 0, 0);
        
        gl.uniform1f(this.uniformLocation['uAlpha'], alpha);
        
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.uniform1i(this.uniformLocation['uSampler'], 0);
	};
	
	LibreGE.program.simpleTexture.prototype.clearBuffer = function(gl){
		gl.deleteBuffer(this.vertexPositionBuffer);
		gl.deleteBuffer(this.textureCoordBuffer);
	};
	
	LibreGE.program.simpleColor = function(){
		var vertexShaderSrc = '\
		    uniform mat4 uPMatrix;\
		    uniform mat4 uMVMatrix;\
			\
			attribute vec3 aVertexPosition;\
			\
		    void main(void) {\
		        gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);\
		    }';
		var fragmentShaderSrc = '\
			#ifdef GL_ES\n\
			precision highp float;\n\
			#endif\n\
			uniform vec4 uColor;\
			\
		    void main(void) {\
				gl_FragColor = uColor;\
		    }';
		
		var p = new LibreGE.Program(vertexShaderSrc, fragmentShaderSrc);
		p.bindData = LibreGE.program.simpleColor.prototype.bindData;
		p.clearBuffer = LibreGE.program.simpleColor.prototype.clearBuffer;
		
		return sp;
	};
	
	LibreGE.program.simpleColor.prototype.bindData = function(gl, pMatrix, mvMatrix, vertexPosition, color){
        gl.uniformMatrix4fv(this.uniformLocation['uPMatrix'], false, pMatrix);
        gl.uniformMatrix4fv(this.uniformLocation['uMVMatrix'], false, mvMatrix);
        
		this.vertexPositionBuffer = this.vertexPositionBuffer || gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexPositionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, vertexPosition, gl.STATIC_DRAW);
        gl.vertexAttribPointer(this.attribLocation['aVertexPosition'], 3, gl.FLOAT, false, 0, 0);

        gl.uniform4fv(this.uniformLocation['uColor'], color);
	};
	
	LibreGE.program.simpleColor.prototype.clearBuffer = function(gl){
		gl.deleteBuffer(this.vertexPositionBuffer);
	};

	/*
	 * 3D camera
	 */
	
	LibreGE.Camera = function(){
		this.position = [0.0, 0.0, 150.0];
		this.lookAt = [0.0, 0.0, 0.0];
		this.up = [0.0, 1.0, 0.0];
		this.FOV = 45;
		this.near = 1.0;
		this.far = 1000.0;
	};
	//TODO:
	
	
	/*
	 * basic node type define
	 */
	var Node = function(){
		this.visible = true;
		this.x = 0;
		this.y = 0;
		this.z = 0;
		this.alpha = 1;
		this.tm = null;//transformation matrix
		this.ctm = null;//current transformation matrix
		this.parent = null;
		this.children = [];
		this.sortfunc = null;
	};
	Node.prototype.update = function(elapsed, ge){};
	Node.prototype.draw = function(context, ge){};
	
	
	/*
	 * built in 3d node type
	 */	
	var Emitter = function(){
		//TODO
	};
	
	var Mesh = function(){
		//TODO
	};

	var Model = function(){
		//TODO
	};
	
	var Scene = function(){
		var node = new Node();
		node.draw = Scene.draw;
		node.onMessage = Scene.onMessage;
		return node;		
	};
	
	Scene.onMessage = function(msg){
		switch(msg.type){
			case LibreGE.msg.POINTER_DOWN : {
//				alert('down');
				break;
			}
			case LibreGE.msg.POINTER_MOVE : {
//				alert('move');
				break;
			}
			case LibreGE.msg.POINTER_UP : {
				alert('up');
				break;
			}
		}
	};
	
	Scene.draw = function(context, ge){
		var gl = context;
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		//TODO draw ground and axis
	};
	
	/*
	 * built in 2d node type
	 */
	
	//Shape
	var Shape2D = function(path, properties){
		var node = new Node();
		node.path = path;
		node.stroke = true;
		node.fill = false;
		node.strokeColor = '#ff0000';
		node.fillColor = '#0000ff';
		node.lineWidth = 1;
		
		if(typeof properties === 'object'){
			LibreGE.utils.setProperties(node, properties);
		}
		
		node.isPointInPath = Shape2D.isPointInPath;
		node.draw = Shape2D.draw;
		
		return node;
	};
	Shape2D.isPointInPath = function(context, x, y){
		LibreGE.utils.create2DShape(context, node.x, node.y, node.path);
		return context.isPointInPath(x, y);
	};
	Shape2D.draw = function(context, ge){
		context.save();
		context.globalAlpha = this.alpha;
		context.lineWidth = this.lineWidth;
		LibreGE.utils.apply2DTransforms(context, this.transforms);
		LibreGE.utils.create2DShape(context, this.x, this.y, this.path);
		if(this.fill){
			context.fillStyle = this.fillColor;
			context.fill();
		}
		if(this.stroke){
			context.strokeStyle = this.strokeColor;
			context.stroke();
		}
		context.restore();
	};
	
	//Image
	var Image2D = function(img, properties){
		var node = new Node();

		node.img = img;
		node.draw = Image2D.draw;
		
		if(typeof properties === 'object'){
			LibreGE.utils.setProperties(node, properties);
		}

		return node;
	};
	Image2D.draw = function(context, ge){
		context.save();
		context.globalAlpha = this.alpha;
		LibreGE.utils.apply2DTransforms(context, this.transforms);
		LibreGE.utils.apply2DShadow(context, this.shadow);
		context.drawImage(this.img, this.x, this.y);
		context.restore();
	};
	
	//Text
	var Text2D = function(txt, properties){
		var node = new Node();
		node.txt = txt;
		node.stroke = false;
		node.font = '12px sans-serif';
		node.color = '#000000';
		node.align = 'left';
		node.baseline = 'top';
		
		if(typeof properties === 'object'){
			LibreGE.utils.setProperties(node, properties);
		}
		
		node.draw = Text2D.draw;
		return node;
	};
	
	Text2D.draw = function(context, ge){
		context.save();
		context.globalAlpha = this.alpha;
		context.font = this.font;
		context.textAlign = this.align;
		context.textBaseline = this.baseline;
		LibreGE.utils.apply2DTransforms(context, this.transforms);
		LibreGE.utils.apply2DShadow(context, this.shadow);
		if(this.stroke){
			context.strokeStyle = this.color;
			context.strokeText(this.txt, this.x, this.y);				
		}else{
			context.fillStyle = this.color;
			context.fillText(this.txt, this.x, this.y);
		}
		context.restore();
	};

	//Scene
	var Scene2D = function(){
		var node = new Node();
		
		var axis = new Shape2D([
			['moveTo', -1000, 10],
			['lineTo', 1000, 10],
			['moveTo', 10, -1000],
			['lineTo', 10, 1000]
		]);
		axis.parent = node;		
		node.children[node.length] = axis;
		
		node.showAxis = function(show){
			axis.visible = show;
		};
		
		node.draw = Scene2D.draw;
		
		return node;
	};
	Scene2D.draw = function(context, ge){
		context.clearRect(0, 0, context.canvas.width, context.canvas.height);
	};
	
	/*
	 * Animation2D
	 * 
	 * action
	 * [
	 *     {matrix, color, alpha, duration, progression},
	 * ]
	 * 
	 */
	
	var Animation2D = function(node, action){
		
		node.action = action;
		node.doUpdate = node.update;		
		node.update = Animation2D.update;
		
		return node;
	};
	Animation2D.update = function(elapsed, ge){
		//....
		this.doUpdate(elapsed, ge);
	};
	

	LibreGE.node = {
		Node : Node,
		Mesh : Mesh,
		Model : Model,
		Scene : Scene,
		Shape2D : Shape2D,
		Image2D : Image2D,
		Text2D : Text2D,
		Scene2D : Scene2D
	};
	
	//export LibreGE to global scope
	window.LibreGE = LibreGE;
	
})();


